package orm

import (
	"api/libs/errs"
	"api/public/global"
	"encoding/json"
	"fmt"
	"gitee.com/dreamwood/ez"
	"gorm.io/gorm"
	"reflect"
	"strings"
)

type Scanner struct {
	Db    *gorm.DB `json:"db"`
	Depth int      `json:"depth"` //扫描深度，0只获取自身非关联字段，1获取自身及第一层关联字段，2....n
	Group string   `json:"group"` //配置分组，用来从ModelConfig中获取关联字段的配置
}

func (s *Scanner) CreateFromArgus(argus []interface{}) {
	for _, arg := range argus {
		tp := reflect.TypeOf(arg)
		switch tp.String() {
		case "int":
			s.Depth = arg.(int)
		case "string":
			s.Group = arg.(string)
		case "*gorm.DB":
			s.Db = arg.(*gorm.DB)
		case "*orm.Scanner":
			cp := arg.(*Scanner)
			s.Depth = cp.Depth - 1
			s.Db = cp.Db
			s.Group = cp.Group
		default:
			ez.Debug(tp.Kind().String())
			ez.Debug(tp.String())
			fmt.Printf("%v", arg)
		}
	}
}

func Scan(v interface{}, argus ...interface{}) reflect.Value {
	//对参数进行处理
	scanner := new(Scanner)
	scanner.Depth = 2
	scanner.Group = "default"
	scanner.Db = global.DB
	scanner.CreateFromArgus(argus)
	var va reflect.Value
	var tp reflect.Type
	tp = reflect.TypeOf(v)
	switch {
	case tp.Kind() == reflect.Pointer:
		//从外界传值进来的
		tp = reflect.TypeOf(v).Elem()
		va = reflect.ValueOf(v).Elem()
	case tp.Kind() == reflect.Struct:
		//区分出是Model还是Value
		if tp.String() == "reflect.Value" {
			//值
			va, _ := v.(reflect.Value)
			tp = reflect.TypeOf(va.Interface())
		} else {
			//model
			tp = reflect.TypeOf(v)
			va = reflect.ValueOf(v)
		}
	default:
		println("scan中出现了未知类型")
	}
	if scanner.Depth == 0 {
		return va.Addr()
	}
	//加载数据
	pkgPath := tp.PkgPath()
	pkg := pkgPath[len("api/app/") : len(pkgPath)-len("/models")] //api/app/demo/models
	ModelConfig := GetEzModelConfig(fmt.Sprintf("%s.%s", pkg, tp.Name()))
	model := va.Addr().Interface()
	relM2M, relM2O := getRelFields(model)
	associations := ez.UnionStrings(relM2O, relM2M)
	configFields := ModelConfig.GetCurdConfig(scanner.Group).Relations
	if len(configFields) > 0 && configFields[0] != "*" {
		associations = ez.IntersectionStrings(associations, configFields)
	}
	id := int(va.FieldByName("Id").Int())
	find, ok := GetCache(id, model)
	if !ok {
		tx := scanner.Db.Model(model).Select(ModelConfig.GetCurdConfig(scanner.Group).Select).
			Omit(ModelConfig.GetCurdConfig(scanner.Group).Omit...)
		for _, association := range associations {
			tx.Preload(association)
		}
		rst := tx.First(model)
		errs.Try(rst.Error)
		SetCache(id, model)
	} else {
		content, e := json.Marshal(find)
		if e != nil {
			ez.PrintError(e)
		} else {
			e = json.Unmarshal(content, &model)
			ez.PrintError(e)
		}
	}
	filledModelVa := reflect.ValueOf(model)
	//加载多对一
	for _, fieldName := range relM2O {
		if !filledModelVa.Elem().FieldByName(fieldName).IsNil() {
			toScan := filledModelVa.Elem().FieldByName(fieldName)
			if !toScan.IsZero() {
				subModel := Scan(toScan.Interface(), scanner)
				if !subModel.IsZero() {
					filledModelVa.Elem().FieldByName(fieldName).Set(subModel)
				}
			}
		}
	}
	//加载多对多
	for _, fieldName := range relM2M {
		if !filledModelVa.Elem().FieldByName(fieldName).IsNil() {
			toScan := filledModelVa.Elem().FieldByName(fieldName)
			for i := 0; i < toScan.Len(); i++ {
				subModel := Scan(toScan.Index(i).Interface(), scanner)
				if !subModel.IsZero() {
					filledModelVa.Elem().FieldByName(fieldName).Index(i).Set(subModel)
				}
			}
		}
	}
	finished := reflect.ValueOf(model).Elem()
	va.Set(finished)
	return va.Addr()
}

func getRelFields(md interface{}) (m2m []string, m2o []string) {
	m2m = make([]string, 0)
	m2o = make([]string, 0)
	tp := reflect.TypeOf(md)
	//判断指针并取出
	switch {
	case tp.Kind().String() == "ptr":
		tp = tp.Elem()
	case tp.Kind().String() == "struct":
		va, ok := md.(reflect.Value)
		if ok {
			va = va.Elem()
			tp = va.Type()
		}
	}
	for i := 0; i < tp.NumField(); i++ {
		fieldType := tp.Field(i).Type.String()
		fieldName := tp.Field(i).Name
		switch {
		case strings.HasPrefix(fieldType, "[]*"):
			m2m = append(m2m, fieldName)
		case strings.HasPrefix(fieldType, "*") && !strings.HasSuffix(fieldType, "time.Time"):
			m2o = append(m2o, fieldName)
		}
	}
	return
}

func dump(v interface{}) {

	tp := reflect.TypeOf(v)
	fmt.Printf("*********************************\n")
	fmt.Printf("Kind：%v\n", tp.Kind().String())
	fmt.Printf("String：%v\n", tp.String())
	fmt.Printf("---------------------------------\n")
	switch tp.Kind() {
	case reflect.Pointer, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
		fmt.Printf("Elem：%v\n", tp.Elem().String())
		fmt.Printf("PkgPath：%v\n", tp.Elem().PkgPath())
		fmt.Printf("Name：%v\n", tp.Elem().Name())
		fmt.Printf("NumField：%v\n", tp.Elem().NumField())
		fmt.Printf("NumMethod：%v\n", tp.Elem().NumMethod())
	case reflect.Struct:
		fmt.Printf("Elem：%v\n", tp.String())
		fmt.Printf("PkgPath：%v\n", tp.PkgPath())
		fmt.Printf("Name：%v\n", tp.Name())
		fmt.Printf("NumField：%v\n", tp.NumField())
		fmt.Printf("NumMethod：%v\n", tp.NumMethod())

	}
	fmt.Printf("Size：%v\n", tp.Size())
	fmt.Printf("*********************************\n")
	//fmt.Printf("Kind：%v\n", tp.Kind().String())
	//fmt.Printf("Elem：%v\n", tp.Elem().String())
	//fmt.Printf("PkgPath：%v\n", tp.Elem().PkgPath())
	//fmt.Printf("Name：%v\n", tp.Elem().Name())
	//fmt.Printf("NumField：%v\n", tp.Elem().NumField())
	//fmt.Printf("NumMethod：%v\n", tp.Elem().NumMethod())
	//fmt.Printf("Size：%v\n", tp.Size())

}
